using System.Text.Json;
using Mapster;
using Microsoft.Extensions.Options;
using SqlSugar;
using Vin.Extension.Framework.Base;
using Vin.Extension.Framework.Extension;
using Vin.Extension.Framework.Models;
using Vin.Extension.Framework.Monitor.Constants;
using Vin.Extension.Framework.Monitor.Dto.Entities;
using Vin.Extension.Framework.Monitor.Dto.List;
using Vin.Extension.Framework.Monitor.Entities;
using Vin.Extension.Framework.Monitor.Models;
using Vin.Extension.Framework.Monitor.Options;
using Vin.Extension.Framework.Monitor.Repository;
using Vin.Extension.Framework.Monitor.ServiceInterface;
using Vin.Extension.JwtBearer.Extension;
using Vin.Extension.JwtBearer.Tool;
using Vin.Tool.Attributes;
using Vin.Tool.Core.AssertCategory;
using Vin.Tool.Core.BasicTypeCategory;
using Vin.Tool.Core.ConvertCategory;
using Vin.Tool.Core.DateTimeCategory;
using Vin.Tool.Core.JsonCategory;
using Vin.Tool.Core.NetCategory;
using Vin.Tool.Domain.ExceptionModel;

namespace Vin.Extension.Framework.Monitor.Service;

public class SysRequestLogService : VinFrameworkBaseService, ISysRequestLogService
{
    [Inject] private readonly SysRequestLogRepository _baseRepository = null!;

    [Inject] private readonly IOptionsMonitor<MonitorOption> _optionsMonitor = null!;
    private MonitorOption Option => _optionsMonitor.CurrentValue;

    [Inject] private readonly MonitorModel _monitorModel = null!;

    public async Task<List<SysRequestLogDto>> ListAsync(SysRequestLogListDto dto)
    {
        dto.StartTime ??= dto.StartTime.ModifiedDateOfNull(-Option.DefaultQueryDays);
        dto.EndTime ??= dto.EndTime.ModifiedDateOfNull();
        if (dto.EndTime!.Value.Subtract(dto.StartTime!.Value).TotalDays > Option.DefaultQueryIntervalDays)
        {
            throw new BusinessException($"查询时间间隔不能超过{Option.DefaultQueryIntervalDays}天");
        }

        var whereExpression = Expressionable.Create<SysRequestLog>()
            .AndIF(dto.Status.HasValue, m => m.Status == dto.Status)
            .AndIF(!string.IsNullOrEmpty(dto.Keyword),
                m => m.RequestId!.Contains(dto.Keyword!) || m.RequestUrl!.Contains(dto.Keyword!) ||
                     m.Exception!.Contains(dto.Keyword!) || m.Ip!.Contains(dto.Keyword!) ||
                     m.RequestUserName!.Contains(dto.Keyword!) ||
                     m.RequestUserId.ToString() == dto.Keyword);
        var list = await _baseRepository.AsQueryable()
            .Where(whereExpression.ToExpression())
            .Select(x => new
            {
                x.Id,
                x.RequestId,
                x.RequestUrl,
                x.RequestMethod,
                x.RequestUserName,
                x.Ip,
                x.Location,
                x.Os,
                x.Browser,
                x.Status,
                x.CostTime,
                x.CustomMsg,
                x.CreateTime
            })
            .ToObjectPageListAsync(dto, x => x.CreateTime, orderByExpression: x => x.CreateTime);
        return list.Adapt<List<SysRequestLogDto>>();
    }

    public async Task<SysRequestLogDto> QueryAsync(long id)
    {
        return (await _baseRepository.GetByIdAsync(id)).Adapt<SysRequestLogDto>();
    }

    public async Task<bool> PreSaveAsync(VinHttpContextInfoModel infoModel, int costTime, object sql)
    {
        var log = new SysRequestLog()
        {
            RequestId = HttpContext.GetRequestId(),
            ControllerPath = infoModel.ControllerPath,
            ActionPath = infoModel.ActionPath,
            RequestUrl = infoModel.Url,
            RequestMethod = infoModel.Method,
            RequestParams = infoModel.Params ?? new { },
            RequestBody = infoModel.Data ?? new { },
            RequestHeader = infoModel.Header ?? new { },
            RequestUser = VinSecurityTool.GetTokenModelStrByToken() ?? new VinTokenModel().ToJson(),
            RequestUserId = HttpContext.GetRealUserId()?.ToLong(),
            RequestUserName = HttpContext.GetUserName(),
            RequestCurl = infoModel.Curl ?? "",
            ResponseCode = infoModel.Code,
            ResponseBody = infoModel.Body ?? new { },
            Exception = infoModel.Exception ?? "",
            Sql = sql,
            Ip = infoModel.Ip,
            Location = infoModel.Location,
            Browser = infoModel.Browser,
            Os = infoModel.Os,
            UserAgent = infoModel.UserAgent ?? "",
            Status = OperationStatus.Success,
            CostTime = costTime,
            CustomMsg = _monitorModel.RequestMsg,
        };
        if (!infoModel.IsSuccess) log.Status = OperationStatus.Fail;
        if (infoModel.IsException) log.Status = OperationStatus.Exception;

        var key = MonitorConstants.RequestLogKey.Format(new Random().Next(0, Option.ThreadSize),
            Guid.NewGuid().ToString("N"));

        return await GetRedisCache().SetCacheAsync(key, log);
    }

    public async Task<bool> SaveAsync(SysRequestLog log)
    {
        var userAgentElement = (JsonElement?)log.RequestHeader;
        if (userAgentElement.HasValue && userAgentElement.Value.TryGetProperty("User-Agent", out var userAgent))
        {
            var clientInfo = HttpContextTool.ParserUserAgent(userAgent.EnumerateArray().FirstOrDefault().GetString()!);
            log.Browser = clientInfo.UA.ToString();
            log.Os = clientInfo.OS.ToString();
            log.UserAgent = clientInfo.String;
            log.Device = clientInfo.Device.ToString();
        }

        log.CustomMsg ??= new List<string>();
        log.RequestParams ??= new { };
        log.RequestBody ??= new { };
        log.RequestHeader ??= new { };
        log.ResponseBody ??= new { };
        log.Exception ??= "";
        log.UserAgent ??= "";

        return await _baseRepository.AsInsertable(log).IgnoreColumnsNull().ExecuteCommandAsync() > 0;
    }
}